package cn.geminis.data.jpa.graphql.utils;

import cn.geminis.core.data.query.Filter;
import cn.geminis.core.data.query.FilterGroup;
import cn.geminis.core.data.query.QueryParameters;
import cn.geminis.core.data.query.Sort;
import graphql.language.*;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author puddi
 */
public class QueryParametersUtils {

    public static QueryParameters getQueryParameters(Field field) {
        var arguments = new ArrayList<>(field.getArguments());

        var queryParameters = QueryParameters.instance();

        handlePage(arguments, queryParameters);
        handleOrder(arguments, queryParameters);
        handleInclude(arguments, queryParameters);
        handleFilter(arguments, queryParameters);
        handleOther(arguments, queryParameters);

        return queryParameters;
    }

    private static void handlePage(List<Argument> arguments, QueryParameters queryParameters) {
        handleArgument(arguments, "page", (ValueHandler<IntValue>) value -> queryParameters.setPageIndex(value.getValue().intValue()));
        handleArgument(arguments, "size", (ValueHandler<IntValue>) value -> queryParameters.setPageSize(value.getValue().intValue()));
    }

    private static void handleOrder(List<Argument> arguments, QueryParameters queryParameters) {
        handleArgument(arguments, "orderBy", (ValueHandler<StringValue>) value -> {
            var values = value.getValue().split("_");
            if (values.length == 1) {
                queryParameters.orderBy(values[0]);
            } else {
                queryParameters.orderBy(values[0], values[1]);
            }
        });

        handleArgument(arguments, "orderBys", (ValueHandler<ArrayValue>) value -> {
            value.getValues().forEach(val -> {
                var objectValue = (ObjectValue) val;
                var values = objectValue.getObjectFields();
                var sort = new Sort();
                handleValue(values, "field", (ValueHandler<StringValue>) v -> sort.setField(v.getValue()));
                handleValue(values, "order", (ValueHandler<StringValue>) v -> sort.setOrder(v.getValue()));
                queryParameters.orderBy(sort.getField(), sort.getOrder());
            });
        });
    }

    private static void handleInclude(List<Argument> arguments, QueryParameters queryParameters) {

    }

    private static void handleFilter(List<Argument> arguments, QueryParameters queryParameters) {
        handleArgument(arguments, "filter", (ValueHandler<ObjectValue>) value -> queryParameters.setFilterGroup(getFilterGroup(value)));
    }

    private static FilterGroup getFilterGroup(ObjectValue value) {
        var filterGroup = new FilterGroup();
        var fields = value.getObjectFields();
        handleValue(fields, "type", (ValueHandler<StringValue>) type -> filterGroup.setType(type.getValue()));
        handleValue(fields, "childGroups", (ValueHandler<ArrayValue>) childGroups -> filterGroup.setChildGroups(
                childGroups.getValues().stream()
                        .map(child -> getFilterGroup((ObjectValue) child))
                        .collect(Collectors.toList())
        ));
        handleValue(fields, "filters", (ValueHandler<ArrayValue>) filters -> filterGroup.setFilters(
                filters.getValues().stream()
                        .map(filter -> getFilter((ObjectValue) filter))
                        .collect(Collectors.toList())));
        return filterGroup;
    }

    private static Filter getFilter(ObjectValue value) {
        var filter = new Filter();
        var fields = value.getObjectFields();
        handleValue(fields, "field", (ValueHandler<StringValue>) v -> filter.setField(v.getValue()));
        handleValue(fields, "compareType", (ValueHandler<StringValue>) v -> filter.setCompareType(v.getValue()));
        handleValue(fields, "value", (ValueHandler<StringValue>) v -> filter.setValue(v.getValue()));
        return filter;
    }

    private static void handleOther(List<Argument> arguments, QueryParameters queryParameters) {
        arguments.forEach(argument -> {
            var values = ((StringValue) argument.getValue()).getValue().split("_");
            if (values.length == 1) {
                queryParameters.where(argument.getName(), values[0]);
            } else {
                queryParameters.where(argument.getName(), values[0], values[1]);
            }
        });
    }

    private static void handleArgument(List<Argument> arguments, String name, ValueHandler handler) {
        arguments.stream()
                .filter(argument -> name.equals(argument.getName()))
                .findFirst()
                .ifPresent(argument -> {
                    handler.handle(argument.getValue());
                    arguments.remove(argument);
                });
    }

    private static void handleValue(List<ObjectField> fields, String name, ValueHandler handler) {
        fields.stream()
                .filter(field -> field.getName().equals(name))
                .findFirst()
                .ifPresent(field -> {
                    handler.handle(field.getValue());
                });
    }

    private interface ValueHandler<T> {
        void handle(T value);
    }

}
